export const Types = {
  TEXT: Symbol("TEXT"),
  COMMENT: Symbol("COMMENT"),
  FRAGEMENT: Symbol("FRAGMENT"),
};

/**
 * @description 对只读的DOM Properties 做特殊处理
 * @param {*} el 元素
 * @param {*} key 属性名
 * @param {*} value 属性值
 */
function shouldSetAsProps(el, key, value) {
  if (key === "form" && el.tagName === "INPUT") {
    return false;
  }

  return key in el;
}

/**
 * @description 正常化 class 值为 对象时，将该对象转 为一字符串
 * @param {*} obj 对象
 * @returns
 */
function classValueObjToString(obj) {
  /* 过滤出 对象中值为 true 的属性 */
  return Object.keys(obj)
    .filter((key) => {
      return obj[key];
    })
    .join(" ");
}

/**
 * @description 正常化 class 值为 数组时，将该对象转 为一字符串
 * @param {*} obj 对象
 * @returns
 */
function classValueArrToString(arr) {
  /* 遍历数据中的元素，如果为对象那么 对其进行 对象转字符串的处理 */
  return arr
    .map((item) => {
      if (typeof item === "object") {
        item = classValueObjToString(item);
      }

      return item;
    })
    .join(" ");
}

/**
 * @description 正常化 class 值 为一字符串
 * @param {*} value class 值
 * @returns { String }
 */
function normalizeClass(value) {
  if (typeof value === "object") {
    value = Array.isArray(value)
      ? classValueArrToString(value)
      : classValueObjToString(value);
  }

  console.log(value);

  return value;
}

/* DOM操作API */
const options = {
  /**
   * @description 创建元素
   * @param {*} tag 标签名
   * @returns { Element }
   */
  createElement(tag) {
    console.log(`创建元素：${tag}`);
    return document.createElement(tag);
  },
  /**
   * @description 设置元素的文本内容
   * @param {*} el 元素
   * @param {*} text 文本内容
   */
  setElementText(el, text) {
    console.log(`设置 ${el.tagName} 元素的文本内容：${text}`);
    el.textContent = text;
  },
  /**
   * @description 将元素插入到容器的参照元素前，如果没有参照元素，则插入到容器的末尾位置
   * @param {*} el 元素
   * @param {*} parent 容器
   * @param {*} anchor 参照元素
   */
  insert(el, parent, anchor = null) {
    console.log(parent);
    parent.insertBefore(el, anchor);
  },

  /**
   * @description 设置元素的属性以及属性值
   * @param {*} el 元素
   * @param {*} key 属性名
   * @param {*} preValue 旧值
   * @param {*} nextValue 新值
   */
  patchProps(el, key, preValue, nextValue) {
    /* 处理事件属性 */
    if (/^on/.test(key)) {
      console.log(key);
      /* 元素身上的所有事件与事件处理函数的映射表 */
      const invokers = el._vei || (el._vei = {});
      /* 当前读取的事件的事件处理函数/事件处理函数集 */
      let invoker = invokers[key];
      /* 事件名称 */
      const name = key.slice(2).toLowerCase();
      console.log(name);
      /* 如果新的虚拟节点上的事件处理函数存在 */
      if (nextValue) {
        /* invoker 伪事件处理函数不存在, 表示第一次绑定该事件  */
        if (!invoker) {
          /* 创建 invoker 并将 伪事件处理函数存储到 el._vei 属性中 */
          invoker = el._vei[key] = (e) => {
            /* 屏蔽事件所有触发时间早于绑定时间的处理函数执行
               解决事件冒泡带来的问题
               e.timeStamp： 事件触发时间
               invoker.attached：事件绑定时间
            */
            if (e.timeStamp < invoker.attached) return;

            /* 执行真正的 事件处理函数 */
            /* 如果取得的是一个数组（一个事件多个事件处理函数），遍历执行所有处理函数 */
            if (Array.isArray(invoker.value)) {
              invoker.value.forEach((fn) => fn(e));
            } else {
              /* 单个处理函数执行 */
              invoker.value(e);
            }
          };

          /* 将事件处理函数存储到 invoker 伪处理函数的 value 属性中 */
          invoker.value = nextValue;

          /* 事件绑定的时间，performance.now()：表示从页面加载开始或 performance.timeOrigin 开始的相对时间 */
          invoker.attached = performance.now();

          /* 给元素添加事件，并绑定事件处理函数 */
          el.addEventListener(name, invoker);
        } else {
          /* 如果新旧处理函数都存在，即新的处理函数替换旧的事件处理函数 */
          invoker.value = nextValue;
        }
      } else {
        /* 如果节点上新的属性没有该事件则删除 */
        el.removeEventListener(name, invoker);
      }
    } else if (key === "class") {
      /* 遇到属性是class，在赋值给className进行特殊处理，再把特殊处理过的字符串赋值给className */
      const value = normalizeClass(nextValue);
      el.className = value || "";
    } else if (shouldSetAsProps(el, key, nextValue)) {
      /* 如果设置的属性是form并且元素是input元素则返回false， 如果HTML元素上存在该属性返回true */
      /* 获取el的属性的类型 */
      const type = typeof el[key];

      /* 该属性（disabled）的值的类型是布尔值，并且即将要设置给该属性的值的新值为空字符串 */
      if (type === "boolean" && value === "") {
        /* 那么设置该属性的值为true，disabled=true 表示禁用表单项 */
        el[key] = true;
      } else {
        /* 否则将新值设置到该属性上（javascript DOM 对象中存在，并为可读可写的属性），有任何值都为不禁用 */
        el[key] = nextValue;
      }
    } else {
      /* 如果HTML元素本身没有这个属性，或者是javascript DOM对象中为只读的属性，那么使用setAttribute来设置自定义属性 */
      el.setAttribute(key, nextValue);
    }
  },

  /**
   * @description 创建文本节点
   * @param {*} text 文本内容
   * @returns
   */
  createText(text) {
    return document.createTextNode(text);
  },

  /**
   * @description 设置文本节点内容
   * @param {*} el 文本节点
   * @param {*} text 文本内容
   */
  setText(el, text) {
    el.nodeValue = text;
  },

  /**
   * @description 创建注释节点
   * @param {*} text 注释内容
   * @returns
   */
  createComment(text) {
    return document.createComment(text);
  },

  /**
   * @description 卸载虚拟节点
   * @param {*} vnode 虚拟DOM节点
   */
  unmount(vnode) {
    /* 片段 */
    if (vnode.type === Types.FRAGEMENT) {
      vnode.children.forEach((child) => options.unmount(child));
      return;
    }

    /* 拿到vnode对应的真实dom元素 */
    const el = vnode.el;
    /* 拿到父元素 */
    const parent = el.parentNode;

    if (parent) {
      parent.removeChild(el);
    }
  },
};

/**
 * @description 创建渲染器
 * @param {*} vnode 虚拟节点
 * @param {*} container 挂载容器
 * @returns
 */
function createRenderer(options) {
  /* 通过 options 得到操作 DOM 的API */
  const {
    createElement,
    setElementText,
    insert,
    patchProps,
    createText,
    setText,
    createComment,
    unmount,
  } = options;
  /**
   * @description 挂载虚拟节点，渲染为真实Dom节点
   * @param {*} vnode
   * @param {*} container
   */
  function mountElement(vnode, container, anchor) {
    /* 创建虚拟节点对应的真实dom节点 */
    /* vnode.el 表示虚拟节点对应的真实dom节点 */
    const el = (vnode.el = createElement(vnode.type));

    /* 处理子节点，如果子节点是字符串，那么该子节点为 文本节点 */
    if (typeof vnode.children === "string") {
      setElementText(el, vnode.children);
    } else if (Array.isArray(vnode.children)) {
      /* 如果children是一个数组，则表示该节点多个子节点 */
      /* 循环遍历children，然后挂载这些子节点 */
      vnode.children.forEach((child) => {
        /* patch的第一个参数为null表示没有旧的节点，表示第一次挂载 */
        patch(null, child, el);
      });
    }

    /* props存在才给元素添加属性 */
    if (vnode.props) {
      /* 遍历虚拟节点对象的props属性，给元素设置属性 */
      for (let key in vnode.props) {
        /* 设置元素的属性以及属性值 */
        patchProps(el, key, null, vnode.props[key]);
      }
    }

    /* 将创建的真实dom节点添加到容器中 */
    insert(el, container, anchor);
  }

  /**
   * @description 更新元素
   * @param {*} oldVnode  旧的虚拟节点
   * @param {*} vnode 新的虚拟节点
   */
  function patchElement(oldVnode, vnode) {
    /* 新旧虚拟节点元素一样的情况下，更新元素的属性 */
    const el = (vnode.el = oldVnode.el);

    /* 存储旧元素节点的属性 */
    const oldProps = oldVnode.props;

    /* 存储新元素节点的属性 */
    const newProps = vnode.props;

    /* 更新元素的属性 */

    /* 遍历新的元素属性对象的键 */
    for (const key in newProps) {
      /* 如果用新的元素属性对象中的键在旧的元素属性对象中获取到的相应的属性的值不一致 */
      if (oldProps[key] !== newProps[key]) {
        /* 更新在旧元素属性对象中的对应属性的值 */
        patchProps(el, key, oldProps[key], newProps[key]);
      }
    }

    /* 遍历旧的元素属性对象的键 */
    for (const key in oldProps) {
      /* 如果旧的元素属性对象的键不存在于新的属性对象，那么删除掉该属性 */
      if (!(key in nextProps)) {
        patchProps(el, key, oldProps[key], null);
      }
    }

    /* 更新子节点 */
    patchChildren(oldVnode, vnode, el);
  }

  /**
   * @description 对比子节点
   * @param {*} oldVnode 旧的虚拟节点
   * @param {*} vnode 新的虚拟节点
   * @param {*} container 容器
   */
  function patchChildren(oldVnode, vnode, container) {
    /* 如果新的子节点是文本 */
    if (typeof vnode.children === "string") {
      /* 如果旧的虚拟节点有子节点并且为非文本节点 */
      if (Array.isArray(oldVnode.children)) {
        /* 删除旧节点的所有元素 */
        oldVnode.children.forEach((child) => {
          unmount(child);
        });
      }
      /* 设置内容为新的文本内容 */
      setElementText(container, vnode.children);
    } else if (Array.isArray(vnode.children)) {
      /* 如果新的虚拟节点是一个数组，代表是非文本节点 */
      if (Array.isArray(oldVnode.children)) {
        /* 双端diff算法 */
        patchKeyedChildren(oldVnode, vnode, container);
        // /* 旧子节点 */
        // const oldChildren = oldVnode.children;
        // /* 新的子节点 */
        // const newChildren = vnode.children;
        // /* 旧节点数组中最大的索引值 */
        // let lastIndex = 0;
        // for (let i = 0; i < newChildren.length; i++) {
        //   /* 新子节点 */
        //   const newVnode = newChildren[i];
        //   /* 在第一层循环中定义 isExist，代表是否在旧的一组节点中找到可复用的节点 */
        //   /* 默认位 false，表示没有找到可复用的节点 */
        //   let isExist = false;
        //   /* 遍历旧子节点，在旧子节点数组中找到与当前新节点相同key的可复用旧子节点 */
        //   for (let j = 0; j < oldChildren.length; j++) {
        //     /* 旧子节点 */
        //     const oldVnode = oldChildren[j];
        //     /* 卸载不存在的子节点 */
        //     const has = newChildren.find(
        //       (newVnode) => newVnode.key === oldVnode.key
        //     );
        //     if (!has) {
        //       unmount(oldVnode);
        //     }
        //     /* 对比新旧子节点的key是否一样 */
        //     if (newVnode.key === oldVnode.key) {
        //       /* 一旦在旧的一组子节点中找到可复用的节点，将 isExist 设置为 true */
        //       /* 表示找到了可复用的节点 */
        //       isExist = true;
        //       /* 找到一样的key的旧节点（可以复用的节点,元素标签一样的节点），将其内容替换为新的内容 */
        //       patch(oldVnode, newVnode, container);
        //       /* key相同的旧节点的索引小于最大索引值，表示需要移动该节点真实DOM的位置 */
        //       if (j < lastIndex) {
        //         /* 获取当前newVnode的前一个 vnode */
        //         const prevVNode = newChildren[i - 1];
        //         /* 如果前一个vnode不存在，表示该 vnode 为第一个 子节点，不需要移动 */
        //         if (prevVNode) {
        //           /* 由于要将newVnode对应的真实DOM移动到prevVNode 所对应真实DOM后面 */
        //           /* 所以我们需要获取prevVNode所对应真实DOM的下一个兄弟节点，并将其作为锚点 */
        //           /* prevVNode.el真实DOM的nextSibling也就是下一个兄弟元素存在即插入到anchor */
        //           /* 不存在的话就追加到父元素的子节点列表的最后面 */
        //           const anchor = prevVNode.el.nextSibling;
        //           /* 调用insert方法将 newVNode 对应的真实DOM 插入到锚点元素的前面 */
        //           /* 也就是 prevVNode 对应真实DOM 的后面 */
        //           insert(newVnode.el, container, anchor);
        //         }
        //       } else {
        //         /* j >= lastIndex 更新lastIndex最大的索引值 */
        //         lastIndex = j;
        //       }
        //       /* 找到key相同的元素（可复用的元素）结束循环 */
        //       break;
        //     }
        //   }
        //   /* 代码运行到这里的话，就代表没在旧的一组子节点中找到可复用的节点 */
        //   if (!isExist) {
        //     /* 在新的一组子节点中找到新节点要挂载的位置 */
        //     /* 获取当前新节点的前一个节点的 */
        //     const prevVNode = newChildren[i - 1];
        //     /* 当前新节点挂载的参考对象 */
        //     let anchor;
        //     /* 如果前一个节点在， 参考 anchor 进行挂载 */
        //     if (prevVNode) {
        //       /* 将该新节点作为当前新节点挂载的参考对象 */
        //       anchor = prevVNode.el.nextSibling;
        //     } else {
        //       /* 如果没有前一个节点，表示当前节点为第一个节点 */
        //       anchor = container.firstChild;
        //     }
        //     patch(null, newVnode, container, anchor);
        //   }
        // }
        /* 旧的子节点数量 */
        // const oldLen = oldChildren.length;
        /* 新的子节点数量 */
        // const newLen = newChildren.length;
        /* 取最小的那个子节点数量作为公共长度（新旧子节点相同的数量）用来更新旧子节点的文本内容 */
        // const commonLen = Math.min(oldLen, newLen);
        /* 遍历更新旧子节点的文本内容 */
        // for (let i = 0; i < commonLen; i++) {
        //   patch(oldChildren[i], newChildren[i], container);
        // }
        /* 如果旧的子节点数量比新的子节点数量少（新的子节点少） */
        // if (oldLen > newLen) {
        //   for (let i = commonLen; i < oldLen; i++) {
        //     /* 那么将旧的多出来的那部分子节点全部卸载掉 */
        //     unmount(oldChildren[i]);
        //   }
        // }
        /* 如果新的子节点比旧的子节点多 */
        // if (newLen > oldLen) {
        //   /* 新挂载多出来的新的子节点 */
        //   for (let i = commonLen; i < newLen; i++) {
        //     patch(null, newChildren[i], container);
        //   }
        // }
      } else {
        /* 清空文本 */
        setElementText(container, "");
        /* 新挂载非文本节点 */
        vnode.children.forEach((child) => {
          patch(null, child, container);
        });
      }
    } else {
      /* 新的虚拟节点没有子节点 */

      /* 如果旧的虚拟节点有子节点并且是非文本节点 */
      if (Array.isArray(oldVnode.children)) {
        /* 那么将旧节点的所有子节点全部卸载 */
        oldVnode.children.forEach((child) => unmount(child));
      } else if (typeof oldVnode.children === "string") {
        /* 如果旧节点的子节点是文本节点 */
        /* 将旧节点的文本内容设置为空 */
        setElementText(container, "");
      }
      /* 如果旧节点没有子节点，则不需要做什么 */
    }
  }

  /**
   * @description diff双端对比算法
   * @param {*} oldVnode 旧虚拟节点
   * @param {*} newVnode 新虚拟节点
   * @param {*} container 容器
   */
  function patchKeyedChildren(oldVnode, newVnode, container) {
    /* 旧的一组子节点 */
    const oldChildren = oldVnode.children;
    /* 新的一组子节点 */
    const newChildren = newVnode.children;
    /* 新的一组子节点的开始索引 */
    let newStartIdx = 0;
    /* 新的一组子节点的结尾索引 */
    let newEndIdx = newChildren.length - 1;
    /* 旧的一组子节点的开始索引 */
    let oldStartIdx = 0;
    /* 旧的一组子节点的结尾索引 */
    let oldEndIdx = oldChildren.length - 1;

    /* 四个索引值指向的vnode节点 */
    let newStartVNode = newChildren[newStartIdx];
    let newEndVNode = newChildren[newEndIdx];
    let oldStartVNode = oldChildren[oldStartIdx];
    let oldEndVNode = oldChildren[oldEndIdx];

    /* 新旧两组节点对比，找到可复用的旧节点 */
    while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
      /* 如果旧的头部节点为undefined，说明被处理过了，跳过接着下一个节点 */
      if (!oldStartVNode) {
        oldStartVNode = oldChildren[++oldStartIdx];
      } else if (!oldEndVNode) {
        /* 如果旧的尾部节点为undefined，说明被处理过了，跳过接着上一个节点 */
        oldEndVnode = oldChildren[--oldEndIdx];
      } else if (oldStartVNode.key === newStartVNode.key) {
        /* 头与头对比 */
        console.log("头头对比，找到可复用的DOM节点");
        /* 只更新节点的属性和子节点...不需要移动真实DOM的位置 */
        patch(oldStartVNode, newStartVNode, container);

        /* 新旧头部节点更新 */
        oldStartVNode = oldChildren[++oldStartIdx];
        newStartVNode = newChildren[++newStartIdx];
      } else if (oldEndVNode.key === newEndVNode.key) {
        console.log("尾尾对比，找到可复用的DOM节点");
        /* 只更新节点的属性和子节点...不需要移动真实DOM的位置 */
        patch(oldEndVNode, newEndVNode, container);

        /* 新旧尾部节点更新*/
        oldEndVNode = oldChildren[--oldEndIdx];
        newEndVNode = newChildren[--newEndIdx];
      } else if (oldEndVNode.key === newStartVNode.key) {
        console.log("旧尾和新头对比，找到可复用的DOM节点");
        patch(oldEndVNode, newStartVNode, container);

        insert(oldEndVNode.el, container, oldStartVNode.el);

        newStartVNode = newChildren[++newStartIdx];
        oldEndVNode = oldChildren[--oldEndIdx];
      } else if (oldStartVNode.key === newEndVNode.key) {
        console.log("旧头和新尾对比，找到可复用的DOM节点");
        /* 更新属性、子节点... */
        patch(oldStartVNode, newEndVNode, container);
        /* 移动真实DOM的位置 */
        insert(oldStartVNode.el, container, oldEndVNode.el.nextSibling);

        /* 更新旧头和新尾元素 */
        oldStartVNode = oldChildren[++oldStartIdx];
        newEndVNode = newChildren[--newEndIdx];
      } else {
        /* 处理非理想状态情况，就是上面四种条件都不符合的情况 */

        /* 使用新的一组子节点的头部节点去旧的一组子节点中去找到可复用的节点 */
        const oldEqualIndex = oldChildren.findIndex(
          (old) => old.key === newStartVNode.key
        );

        /* 如果大于0，表示存在可复用的节点 */
        if (oldEqualIndex > 0) {
          /* 可复用的节点 */
          const reuseVNode = oldChildren[oldEqualIndex];
          /* 更新可复用节点的属性、子节点... */
          patch(reuseVNode, newStartVNode, container);
          /* 移动可复用的节点对应真实DOM的位置 */
          insert(reuseVNode.el, container, oldStartVNode.el);

          /* 因为可复用的节点对应的真实DOM已被移动到了别处，所以将其设置为undefined */
          oldChildren[oldEqualIndex] = undefined;
        } else {
          /* 没有找到可复用的节点，表示该节点是新增的节点 */
          patch(null, newStartVNode, container, oldStartVNode.el);
        }

        /* 更新新的一组子节点中的头部节点 */
        newStartVNode = newChildren[++newStartIdx];
      }
    }

    /* 循环结束检查索引值的情况 */
    if (oldEndIdx < oldStartIdx && newStartIdx <= newEndIdx) {
      /* 如果满足条件，则说明有新的节点遗留，需要挂载它们 */
      for (let i = newStartIdx; i <= newEndIdx; i++) {
        const anchor = newChildren[newEndIdx + 1]
          ? newChildren[newEndIdx + 1].el
          : null;
        patch(null, newChildren[i], container, anchor);
      }
    } else if (newEndIdx < newStartIdx && oldStartIdx <= oldEndIdx) {
      for (let i = oldStartIdx; i <= oldEndIdx; i++) {
        /* 如果满足条件，则说明有旧的节点遗留，需要卸载它们 */
        unmount(oldChildren[i]);
      }
    }
  }

  /**
   * @description 挂载、更新虚拟节点
   * @param {*} oldVnode 旧虚拟节点
   * @param {*} vnode 新虚拟节点
   * @param {*} container 挂载容器
   */
  function patch(oldVnode, vnode, container, anchor) {
    /* 如果非第一次挂载，并且新旧vnode的内容不一样（即元素不一样） */
    if (oldVnode && oldVnode.type !== vnode.type) {
      unmount(oldValue);
      oldVnode = null;
    }

    const { type } = vnode;

    /* 代码能运行到这的话，表示旧vnode和新vnode的内容（元素）是一样的 */
    /* 如果类型是字符串，代表是 element 元素 */
    if (typeof type === "string") {
      /* 如果旧的 vnode 不存在，表示第一次挂载，则调用 mountElement 函数完成真实dom节点挂载 */
      if (!oldVnode) {
        /* 第一次挂载 */
        mountElement(vnode, container, anchor);
      } else {
        /* 旧的vnode存在即表示更新 */
        patchElement(oldVnode, vnode);
      }
    } else if (type === Types.TEXT) {
      if (!oldVnode) {
        /* 第一次挂载，创建文本节点 */
        const el = (vnode.el = createText(vnode.children));
        /* 将文本节点添加到容器中 */
        insert(el, container);
      } else {
        /* 获取到节点 */
        const el = (vnode.el = oldVnode.el);
        /* 新旧内容不一致，替换内容 */
        if (vnode.children !== oldVnode.children) {
          setText(el, vnode.children);
        }
      }
    } else if (type === Types.COMMENT) {
      /* 注释节点 */

      if (!oldVnode) {
        /* 第一次挂载 */
        const el = (vnode.el = createComment(vnode.children));
        /* 将注释节点添加到容器中 */
        insert(el, container);
      } else {
        /* 获取到节点 */
        const el = (vnode.el = oldVnode.el);
        /* 新旧内容不一致，替换内容 */
        if (vnode.children !== oldVnode.children) {
          setText(el, vnode.children);
        }
      }
    } else if (type === Types.FRAGEMENT) {
      /* 片段 */

      if (!oldVnode) {
        vnode.children.forEach((child) => patch(null, child, container));
      } else {
        patchChildren(oldVnode, vnode, container);
      }
    } else if (typeof type === "object") {
      /* vnode的type是一个对象，描述的是一个组件 */
      console.log("vnode是一个组件");
    }
  }

  /**
   * @description 渲染函数
   * @param {*} vnode 虚拟节点
   * @param {*} container 挂载容器
   */
  function render(vnode, container) {
    /* 传了新的虚拟节点 */
    if (vnode) {
      /* 挂载或更新真实节点，挂载：container._vnode 为 undefined，更新：container._vnode 为 上一次渲染的 vnode */
      patch(container._vnode, vnode, container);
    } else {
      /* 旧的vnode存在并且没有传新的vnode，说明是卸载操作 */
      if (container._vnode) {
        unmount(container._vnode);
      }
    }

    /* 把vnode存储到 container._vnode下，即后续渲染中的旧 vnode */
    container._vnode = vnode;
  }

  return {
    render,
  };
}

export const renderer = createRenderer(options);
